package blob

import (
	"context"
	"coolcar/v1/blob/api/gen/v1"
	"coolcar/v1/blob/dao"
	"coolcar/v1/shared/ids"
	"go.mongodb.org/mongo-driver/mongo"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"io"
	"io/ioutil"
	"net/http"
	"time"
)

// Service defines a blob service.
type Service struct {
	Sugar *zap.SugaredLogger
	Mongo *dao.Mongo
	Storage
}

// Storage defines Storage interface.
type Storage interface {
	SignUrl(ctx context.Context, method, path string, timeout time.Duration) (string, error)
	Get(ctx context.Context, path string) (io.ReadCloser, error)
}

// CreateBlob creates a blob.
func (s *Service) CreateBlob(ctx context.Context, request *blobpb.CreateBlobRequest) (*blobpb.CreateBlobResponse, error) {
	aid := ids.AccountID(request.AccountId)
	br, err := s.Mongo.CreateBlob(ctx, aid)
	if err != nil {
		s.Sugar.Errorf("failed to create blob :%v", err)
		return nil, status.Error(codes.Internal, "")
	}
	url, err := s.Storage.SignUrl(ctx, http.MethodPut, br.Path, secToDuration(request.UploadUrlTimeoutSec))
	if err != nil {
		return nil, status.Errorf(codes.Aborted, "failed sign url: %v", err)
	}
	return &blobpb.CreateBlobResponse{
		Id:        br.ID.Hex(),
		UploadUrl: url,
	}, nil
}

// GetBlob get a blob.
func (s *Service) GetBlob(ctx context.Context, request *blobpb.GetBlobRequest) (*blobpb.GetBlobResponse, error) {
	bid := ids.BlobID(request.Id)
	br, err := s.GetBlobRecord(ctx, bid)
	if err != nil {
		return nil, err
	}
	r, err := s.Storage.Get(ctx, br.Path)
	if r != nil {
		defer r.Close()
	}
	if err != nil {
		return nil, status.Errorf(codes.Aborted, "failed to storage: %v", err)
	}
	bytes, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, status.Errorf(codes.Aborted, "failed to storage: %v", err)
	}
	return &blobpb.GetBlobResponse{
		Date: bytes,
	}, nil
}

// GetBlobURL get a blob URL.
func (s *Service) GetBlobURL(ctx context.Context, request *blobpb.GetBlobURLRequest) (*blobpb.GetBlobURLResponse, error) {
	bid := ids.BlobID(request.Id)
	br, err := s.GetBlobRecord(ctx, bid)
	if err != nil {
		return nil, err
	}
	url, err := s.SignUrl(ctx, http.MethodGet, br.Path, secToDuration(request.TimeoutSec))
	if err != nil {
		return nil, status.Errorf(codes.Aborted, "failed sign url: %v", err)
	}
	return &blobpb.GetBlobURLResponse{
		Url: url,
	}, nil
}

func (s *Service) GetBlobRecord(ctx context.Context, bid ids.BlobID) (*dao.BlobRecord, error) {
	br, err := s.Mongo.GetBlob(ctx, bid)
	if err == mongo.ErrNoDocuments {
		return nil, status.Error(codes.NotFound, "")
	}
	if err != nil {
		return nil, status.Error(codes.InvalidArgument, err.Error())
	}
	return br, nil
}

func secToDuration(sec int32) time.Duration {
	return time.Duration(sec) * time.Second
}
